/**
 * 
 */
package com.googlecode.dynargs.extractor;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import com.googlecode.dynargs.model.constraints.Constraint;
import com.googlecode.dynargs.model.type.Type;
import com.googlecode.dynargs.util.IndentedWriter;

/**
 * @author "Federico De Faveri defaveri@gmail.com"
 *
 */
public class ModelExtractor {

	protected IndentedWriter logger;

	protected Map<String, Type> typesCache;

	protected List<TypeExtractor<?>> extractors;

	protected List<ConstraintExtractor> constraintExtractors;

	protected Stack<String> underAnalisys;

	public ModelExtractor()
	{
		typesCache = new HashMap<String, Type>();
		underAnalisys = new Stack<String>();
		logger = new IndentedWriter();
		extractors = new ArrayList<TypeExtractor<?>>();
		constraintExtractors = new ArrayList<ConstraintExtractor>();
		registerDefaultExtractors();
	}

	protected void registerDefaultExtractors()
	{
		extractors.add(new ArrayExtractor());
		extractors.add(new CollectionExtractor());
		extractors.add(new EnumerationExtractor());
		extractors.add(new JavaBaseTypesExtractor());
		extractors.add(new ReflectionExtractor());

		constraintExtractors.add(new ValidationConstraintsExtractor());
	}

	public TypeExtractor<?> findExtractor(TypeToken type) {
		for (TypeExtractor<?> typeExtractor:extractors) {
			if (typeExtractor.canExtract(type)) return typeExtractor;
		}
		return null;
	}

	public Type extract(Class<?> clazz)
	{
		return extract(new TypeToken(clazz));
	}

	public Type extract(TypeToken javaType)
	{
		TypeExtractor<?> extractor = findExtractor(javaType);
		if (extractor == null) {
			throw new IllegalArgumentException("Extractor not found for type "+javaType);
		}

		//System.out.println("extractor: "+extractor);

		Type type = extractor.extract(this, javaType);
		typesCache.put(type.getId(), type);
		return type;
	}

	public Type getFromTypesCache(String id)
	{
		return typesCache.get(id);
	}

	protected ConstraintExtractor findConstraintExtractor(Field field, Annotation annotation)
	{
		for (ConstraintExtractor extractor:constraintExtractors) {
			if (extractor.canExtract(field, annotation)) return extractor;
		}
		return null;
	}

	public List<Constraint> extractConstraints(Field field)
	{
		Annotation[] annotations = field.getAnnotations();
		if (annotations.length == 0) return Collections.emptyList();
		
		List<Constraint> constraints = new ArrayList<Constraint>();
		for (Annotation annotation:annotations) {
			ConstraintExtractor extractor = findConstraintExtractor(field, annotation); 
			if (extractor!=null) {
				Constraint constraint = extractor.extract(field, annotation);
				constraints.add(constraint);
			}
		}
		return constraints;
	}

	protected IndentedWriter getLogger()
	{
		return logger;
	}

}
